/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 *
 * The Initial Developer of the Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 * Portions created by Mark A. Kobold are Copyright (C) 2000-2007. All Rights Reserved.
 *
 * Contributor(s): 
 *  Mark A. Kobold [mkobold <at> isqlviewer <dot> com].
 *  
 * If you didn't download this code from the following link, you should check
 * if you aren't using an obsolete version: http://www.isqlviewer.com
 */
package org.isqlviewer.event;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.EventObject;
import java.util.Vector;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.EventListenerList;

/**
 * TODO Add EventDispatcher Overview JavaDoc.
 * <p>
 * 
 * @author Mark A. Kobold &lt;mkobold at isqlviewer dot com&gt;
 * @version 1.0
 */
public final class EventDispatcher implements EventProxyDelegate {

    private Vector<EventQueueEntry> eventQueue = new Vector<EventQueueEntry>();
    private Object queueLatch = new Object();
    private EventListenerList eventDelegates = new EventListenerList();
    private EventLoop eventLoop = new EventLoop();
    private Thread eventThread = null;

    public EventDispatcher() {

        eventThread = new Thread(eventLoop, "event-queue");
        eventThread.setDaemon(true);
        eventThread.setPriority(Thread.NORM_PRIORITY);
        eventThread.start();
    }

    public synchronized void halt() {

        if (eventThread != null) {
            eventThread.interrupt();
        }

        synchronized (queueLatch) {
            queueLatch.notifyAll();
        }
    }

    public void addProgressListener(ProgressListener eventListener) {

        eventDelegates.add(ProgressListener.class, eventListener);
    }

    public void removeProgressListener(ProgressListener eventListener) {

        eventDelegates.remove(ProgressListener.class, eventListener);
    }

    public void addDatabaseListener(DatabaseListener eventListener) {

        eventDelegates.add(DatabaseListener.class, eventListener);
    }

    public void removeDatabaseListener(DatabaseListener eventListener) {

        eventDelegates.remove(DatabaseListener.class, eventListener);
    }

    public void addChangeListener(ChangeListener eventListener) {

        eventDelegates.add(ChangeListener.class, eventListener);
    }

    public void removeChangeListener(ChangeListener eventListener) {

        eventDelegates.remove(ChangeListener.class, eventListener);
    }

    public void addComponentListener(ComponentListener eventListener) {

        eventDelegates.add(ComponentListener.class, eventListener);
    }

    public void removeComponentListener(ComponentListener eventListener) {

        eventDelegates.remove(ComponentListener.class, eventListener);
    }

    public void addActionListener(ActionListener eventListener) {

        eventDelegates.add(ActionListener.class, eventListener);
    }

    public void removeActionListener(ActionListener eventListener) {

        eventDelegates.remove(ActionListener.class, eventListener);
    }

    public void stateChanged(ChangeEvent event) {

        enqueueEvent(ChangeListener.class, ChangeEvent.class, event, "stateChanged");
    }

    public void actionPerformed(ActionEvent event) {

        enqueueEvent(ActionListener.class, ActionEvent.class, event, "actionPerformed");
    }

    public void componentHidden(ComponentEvent event) {

        enqueueEvent(ComponentListener.class, ComponentEvent.class, event, "componentHidden");
    }

    public void componentMoved(ComponentEvent event) {

        enqueueEvent(ComponentListener.class, ComponentEvent.class, event, "componentMoved");
    }

    public void componentResized(ComponentEvent event) {

        enqueueEvent(ComponentListener.class, ComponentEvent.class, event, "componentResized");
    }

    public void componentShown(ComponentEvent event) {

        enqueueEvent(ComponentListener.class, ComponentEvent.class, event, "componentShown");
    }

    public void databaseStateChanged(DatabaseEvent event) {

        enqueueEvent(DatabaseListener.class, DatabaseEvent.class, event, "databaseStateChanged");
    }

    public void progressUpdated(ProgressEvent event) {

        enqueueEvent(ProgressListener.class, ProgressEvent.class, event, "progressUpdated");
    }

    private synchronized void enqueueEvent(Class< ? extends EventListener> listener,
            Class< ? extends EventObject> event, Object argument, String method) {

        EventQueueEntry eqe = new EventQueueEntry(listener, argument, event, method);
        eventQueue.add(eqe);
        synchronized (queueLatch) {
            queueLatch.notifyAll();
        }
    }

    private class EventLoop implements Runnable {

        public void run() {

            Thread process = Thread.currentThread();
            main : while (!process.isInterrupted()) {
                if (eventQueue.isEmpty()) {
                    synchronized (queueLatch) {
                        try {
                            queueLatch.wait();
                        } catch (InterruptedException error) {
                            break main;
                        }
                    }
                }
                Enumeration queueCursor = eventQueue.elements();
                while (queueCursor.hasMoreElements()) {
                    EventQueueEntry event = eventQueue.remove(0);
                    if (event == null) {
                        continue;
                    }
                    EventListener[] delegates = eventDelegates.getListeners(event.listenerType);
                    Object[] eventArguments = new Object[]{event.eventArgument};
                    for (int i = 0; i < delegates.length; i++) {
                        try {
                            Method eventMethod = event.getMethod();
                            eventMethod.invoke(delegates[i], eventArguments);
                        } catch (InvocationTargetException ite) {

                        } catch (Exception error) {
                            error.printStackTrace();
                        } catch (Error fatalError) {
                            break main;
                        }
                    }
                }
            }
        }
    }

    private static class EventQueueEntry {

        Class< ? extends EventObject> eventArgumentType = null;
        Class< ? extends EventListener> listenerType = null;
        Object eventArgument = null;
        String eventMethod = null;

        EventQueueEntry(Class< ? extends EventListener> listener, Object argument, Class< ? extends EventObject> type,
                String method) {

            this.listenerType = listener;
            this.eventArgument = argument;
            this.eventArgumentType = type;
            this.eventMethod = method;
        }

        public Method getMethod() throws NoSuchMethodException {

            return listenerType.getMethod(eventMethod, new Class[]{eventArgumentType});
        }

    }

}
